package cn.easyshipping.cloud.config.support;

import brave.propagation.TraceContext;
import cn.easyshipping.cloud.business.dto.BaseRequestBody;
import cn.easyshipping.cloud.common.log.ApiLogInfo;
import cn.easyshipping.cloud.common.log.OperationLog;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.List;
import java.util.UUID;

/**
 * @author: bwl
 * created on: 2020/4/13 8:46 下午
 * description: 自定义参数解析在到达控制器请求之前
 * 控制器注解 1.如需要获取BaseRequestBody里的参数 传递对象格式BaseRequestBody<ReqSms> reqSms  传递集合格式BaseRequestBody<List<ReqSms>> reqSms
 * 2.如不需要获取BaseRequestBody里的参数 传递对象格式ReqSms reqSms  传递集合格式<List<ReqSms>> reqSms
 */
@Component
public class BaseParamsArgumentResolver implements HandlerMethodArgumentResolver {

    private static Logger log = LoggerFactory.getLogger(OperationLog.class.getSimpleName());

    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer,
                                  NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
        Class<?> c = methodParameter.getParameterType();
        //解析List<ReqSms> 或者ReqSms 形式参数
        if (!c.equals(BaseRequestBody.class)) {
            final Type genType = methodParameter.getGenericParameterType();
            String result = getRequestResult(request).toString();
            JSONObject jsonObject = (JSONObject) JSONObject.parse(result);
            String postData = jsonObject.getString("PostData");
            if (genType instanceof ParameterizedType) {
                Type type = ((ParameterizedType) genType).getActualTypeArguments()[0];
                final List<?> list = JSON.parseArray(postData, Class.forName(type.getTypeName()));
                return list;
            } else if (genType instanceof Class) {
                final Object o = JSON.parseObject(postData, genType);
                return o;
            } else {
                final String o = JSON.parseObject(postData, String.class);
                return o;
            }
        } else {
            //解析BaseRequestBody<List<ReqSms>> reqSms 或者BaseRequestBody<ReqSms> reqSms 形式参数
            String requestBody = getRequestResult(request).toString();
            BaseRequestBody jsonObject = JSONObject.parseObject(requestBody, BaseRequestBody.class);
            String parameterJson = jsonObject.getPostData().toString();
            BaseRequestBody baseReq = new BaseRequestBody();
            final Type genType = methodParameter.getGenericParameterType();
            Type type = ((ParameterizedType) genType).getActualTypeArguments()[0];
            if (type instanceof ParameterizedType) {
                final List<?> list = JSON.parseArray(parameterJson, Class.forName(((ParameterizedType) type).getActualTypeArguments()[0].getTypeName()));
                baseReq.setPostData(list);
            } else if (type instanceof Class) {
                final Object o = JSON.parseObject(parameterJson, type);
                baseReq.setPostData(o);
            } else {
                final String o = JSON.parseObject(parameterJson, String.class);
                baseReq.setPostData(o);
            }
            baseReq.setPltCode(jsonObject.getPltCode());
            baseReq.setPostTime(jsonObject.getPostTime());
            baseReq.setReqCode(jsonObject.getReqCode());
            baseReq.setSerialNo(jsonObject.getSerialNo());
            baseReq.setVersion(jsonObject.getVersion());
            validateParams(getServletRequestDataBinder(methodParameter, nativeWebRequest, webDataBinderFactory, baseReq), nativeWebRequest, requestBody, methodParameter);
            validateParams(getServletRequestDataBinder(methodParameter, nativeWebRequest, webDataBinderFactory, baseReq.getPostData()), nativeWebRequest, requestBody, methodParameter);
            return baseReq;
        }
    }

    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        final Class<?> containingClass = methodParameter.getContainingClass();
        final Class superClass = containingClass.getSuperclass();
        if (superClass.equals(BaseRequestBody.class)) {
            return true;
        }
        final Type genType = methodParameter.getGenericParameterType();
        if (genType instanceof Class) {
            if (((Class) genType).getName().equals(String.class.getName())) {
                return false;
            }
        }
        if (genType instanceof ParameterizedTypeImpl) {
            String baseRequestName = ((ParameterizedTypeImpl) genType).getRawType().getName();
            if (baseRequestName.equals(BaseRequestBody.class.getName())) {
                return true;
            }
        }
        return false;
    }

    private ServletRequestDataBinder getServletRequestDataBinder(MethodParameter parameter, NativeWebRequest webRequest, WebDataBinderFactory binderFactory, Object object) throws Exception {
        return (ServletRequestDataBinder) binderFactory.createBinder(webRequest, object, parameter.getParameterName());
    }

    /**
     * 进行参数校验
     */
    private void validateParams(ServletRequestDataBinder binder, NativeWebRequest webRequest, String requestBody, MethodParameter methodParameter) throws MethodArgumentNotValidException {
        binder.bind(webRequest.getNativeRequest(ServletRequest.class));
        binder.validate();
        BindingResult bindingResult = binder.getBindingResult();
        if (bindingResult.getFieldErrorCount() > 0) {
            String errorMsg;
            if (bindingResult.getFieldError().isBindingFailure()) {
                errorMsg = String.format("参数[%s]类型不匹配", bindingResult.getFieldError().getField());
            } else {
                errorMsg = bindingResult.getFieldError().getDefaultMessage();
            }
            writeErrorLog(requestBody, errorMsg);
            throw new MethodArgumentNotValidException(methodParameter, bindingResult);
        }
    }

    private StringBuilder getRequestResult(HttpServletRequest request) throws IOException {
        BufferedReader reader = request.getReader();
        StringBuilder sb = new StringBuilder();
        char[] buf = new char[1024];
        int rd;
        while ((rd = reader.read(buf)) != -1) {
            sb.append(buf, 0, rd);
        }
        return sb;
    }

    private void writeErrorLog(String requestBody, String responseBody) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String requestUrl = request.getRequestURL().toString();
        Date now = new Date();

        String traceId = "";
        Object traceContext = request.getAttribute("brave.propagation.TraceContext");
        //Zipkin链路追踪用的TraceId
        if (traceContext instanceof TraceContext) {
            traceId = ((TraceContext) traceContext).traceIdString();
        }

        ApiLogInfo apiLogInfo = new ApiLogInfo();
        apiLogInfo.setLogID(UUID.randomUUID().toString());
        apiLogInfo.setTraceID(traceId);
        apiLogInfo.setApiName(request.getServletPath());
        apiLogInfo.setRequestMethod(request.getMethod());
        apiLogInfo.setHeadAuth(request.getHeader("x-ryd-authorization"));
        String app="";
        if (request.getQueryString()!=null){
            app="?"+request.getQueryString();
        }
        apiLogInfo.setRequestUrl(requestUrl + app);
        apiLogInfo.setPostData(requestBody);
        apiLogInfo.setRequestTime(now);
        apiLogInfo.setUnixTS(now.getTime());
        apiLogInfo.setResponseData(responseBody);
        apiLogInfo.setResponseTime(now);

        log.error(JSON.toJSONString(apiLogInfo));
    }
}
